using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace cb0tServer
{
    class RoomSearch
    {
        private static Thread thread;
        private static List<RoomSearchObject> items = new List<RoomSearchObject>();

        public static bool terminate = false;

        public static int DBSize
        {
            get { return items.Count; }
        }

        public static void Query(UserObject userobj, String query)
        {
            if (items.Count < 2)
            {
                UserPool.Broadcast(userobj.vroom, ServerOutboundPackets.AnnoucePacket("sb0t is building a channel list database for the first time, please try again in a few minutes"));
                return;
            }

            String q = query.ToUpper();

            List<RoomSearchObject> qlist = items.FindAll(delegate(RoomSearchObject r)
            {
                return r.server.ToUpper().Contains(q) || r.name.ToUpper().Contains(q) || r.topic.ToUpper().Contains(q);
            });

            if (qlist.Count == 0)
            {
                UserPool.Broadcast(userobj.vroom, ServerOutboundPackets.AnnoucePacket("Unable to find any results containing " + query));
            }
            else
            {
                if (qlist.Count > 1)
                    qlist.Sort(delegate(RoomSearchObject x, RoomSearchObject y)
                    {
                        if (x.usercount == y.usercount)
                            return 0;

                        return x.usercount > y.usercount ? -1 : 1;
                    });

                UserPool.Broadcast(userobj.vroom, ServerOutboundPackets.AnnoucePacket("\x0007\x000306Results for \x000303" + query + " \x000306as follows"));
                int count = 0;

                foreach (RoomSearchObject r in qlist)
                {
                    UserPool.Broadcast(userobj.vroom, ServerOutboundPackets.AnnoucePacket(""));
                    UserPool.Broadcast(userobj.vroom, ServerOutboundPackets.AnnoucePacket("\x000303Name\x000306: \x000314" + r.name));
                    UserPool.Broadcast(userobj.vroom, ServerOutboundPackets.AnnoucePacket("\x000303Topic\x000306: \x000314" + r.topic));
                    UserPool.Broadcast(userobj.vroom, ServerOutboundPackets.AnnoucePacket("\x000303Language\x000306: \x000314" + r.language + " \x000306| \x000303Server\x000306: \x000314" + r.server + " \x000306| \x000303Users\x000306: \x000314" + r.usercount));
                    UserPool.Broadcast(userobj.vroom, ServerOutboundPackets.AnnoucePacket("\x000303Hashlink\x000306: \x000314\\\\" + r.hashlink));

                    if (count++ > 4)
                        break;
                }
            }
        }

        public static void InitializeBackgroundWorker()
        {
            thread = new Thread(new ThreadStart(BackgroundWorker));
            thread.Start();
        }

        private static void BackgroundWorker()
        {
            terminate = false;
            String filename = sb0t_winGUI.RunOnceFileUpdater.sbotpath + "data/channellist1.dat";
            List<RoomSearchObject> servers = new List<RoomSearchObject>();

            try
            {
                if (File.Exists(filename))
                {
                    servers = new List<RoomSearchObject>(Bytes2RSObjectArray(File.ReadAllBytes(filename), true));
                    items = new List<RoomSearchObject>(servers.ToArray());
                }
                else servers = new List<RoomSearchObject>(Bytes2RSObjectArray(sb0t_winGUI.Properties.Resources.servers, false)); // updated
            }
            catch { }

            uint last_list_grab = (Helpers.UnixTime() - 1800);

            while (true)
            {
                if (terminate)
                    return;

                uint now = Helpers.UnixTime();

                if ((last_list_grab + 1800) < now) // wake up
                {
                    if (items.Count > 0)
                        servers = new List<RoomSearchObject>(items.ToArray());

                    List<RoomSearchObject> accepted = new List<RoomSearchObject>();
                    Socket usock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                    usock.Blocking = false;
                    int pos = 0;
                    int timeout = 0;
                    uint last_received = Helpers.UnixTime();

                    while (true)
                    {
                        uint time = Helpers.UnixTime();

                        if (terminate)
                            return;

                        int to_now = (int)((last_received + 10) - time);

                        if (to_now != timeout)
                            timeout = to_now;

                        if ((last_received + 10) < time || to_now == 0)
                            break;

                        if (pos < servers.Count)
                        {
                            try
                            {
                                usock.SendTo(new byte[] { 2 }, (EndPoint)new IPEndPoint(servers[pos].ip, servers[pos].port));
                            }
                            catch { }
                            pos++;
                            last_received = time;
                        }

                        Thread.Sleep(50);

                        if (usock.Available > 0)
                        {
                            EndPoint ep = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
                            byte[] buf1 = new byte[1024];
                            int size = 0;

                            try
                            {
                                size = usock.ReceiveFrom(buf1, ref ep);
                            }
                            catch { }

                            if (size > 0)
                            {
                                try
                                {
                                    UdpPacketReader packet = new UdpPacketReader(new List<byte>(buf1).GetRange(0, size).ToArray());

                                    if (packet.ReadByte() == 3)
                                    {
                                        RoomSearchObject cobj = new RoomSearchObject();
                                        cobj.ip = ((IPEndPoint)ep).Address;

                                        if (AutoLogin.IsHost(cobj.ip))
                                            cobj.ip = IPAddress.Loopback;

                                        cobj.port = packet.ReadInt16();
                                        cobj.usercount = packet.ReadInt16();
                                        cobj.name = packet.ReadString();
                                        cobj.topic = packet.ReadString();
                                        cobj.language = LanguageCodeToString(packet.ReadByte());
                                        cobj.server = ExtractServerName(packet.ReadString());
                                        packet.ReadByte();

                                        while (packet.Remaining() >= 6)
                                        {
                                            RoomSearchObject _server = new RoomSearchObject();
                                            _server.ip = packet.ReadIP();
                                            _server.port = packet.ReadInt16();

                                            if (AutoLogin.IsHost(_server.ip))
                                                _server.ip = IPAddress.Loopback;

                                            if (servers.Find(delegate(RoomSearchObject s) { return s.ip.Equals(_server.ip); }) == null)
                                                servers.Add(_server);
                                        }

                                        last_received = time;
                                        Ares.CommonObjects.ChannelListItem ch = new Ares.CommonObjects.ChannelListItem();
                                        ch.externalIp = cobj.ip.Equals(IPAddress.Loopback) ? CoreSettings.room_external_ip : cobj.ip;
                                        ch.localIp = ch.externalIp;
                                        ch.name = cobj.name;
                                        ch.topic = "";
                                        ch.port = cobj.port;
                                        ch.users = cobj.usercount;
                                        cobj.hashlink = "arlnk://" + Ares.Protocols.Hashlinks.EncodeHashlink(ch);

                                        if (!(cobj.usercount >= 200 && cobj.server != "sb0t")) // noob server spoils the channel list
                                            accepted.Add(cobj);
                                    }
                                }
                                catch { }
                            }
                        }

                        Thread.Sleep(50);
                    }

                    if (accepted.Count > 2)
                    {
                        items = new List<RoomSearchObject>(accepted.ToArray());

                        if (items.Find(delegate(RoomSearchObject r) { return r.ip.Equals(IPAddress.Loopback); }) == null)
                        {
                            RoomSearchObject r = new RoomSearchObject();
                            r.ip = IPAddress.Loopback;
                            r.port = CoreSettings.room_port;
                            r.name = CoreSettings.room_name;
                            r.topic = Topics.GetMainTopic(true);
                            r.server = "sb0t";
                            r.language = LanguageCodeToString(CoreSettings.room_language);
                            r.usercount = UserPool.UserCount();
                            r.hashlink = "arlnk://" + Ares.Protocols.Hashlinks.EncodeHashlink(new Ares.CommonObjects.ChannelListItem(r.name, r.name, r.port, 1, CoreSettings.room_external_ip, CoreSettings.room_external_ip));
                            items.Add(r);
                        }

                        SaveChannelList();
                    }

                    last_list_grab = Helpers.UnixTime();
                }

                Thread.Sleep(50);
            }
        }

        private static RoomSearchObject[] Bytes2RSObjectArray(byte[] data, bool details)
        {
            List<RoomSearchObject> list = new List<RoomSearchObject>();
            Ares.PacketHandlers.AresDataPacket reader = new Ares.PacketHandlers.AresDataPacket(data);

            try
            {
                while (reader.Remaining() > 0)
                {
                    RoomSearchObject r = new RoomSearchObject();

                    if (details)
                    {
                        r.name = reader.ReadString();
                        r.topic = reader.ReadString();
                        r.server = reader.ReadString();
                        r.language = reader.ReadString();
                        r.hashlink = reader.ReadString();
                        r.usercount = reader.ReadInt16();
                    }

                    r.ip = reader.ReadIP();
                    r.port = reader.ReadInt16();

                    if (AutoLogin.IsHost(r.ip))
                        r.ip = IPAddress.Loopback;

                    list.Add(r);
                }
            }
            catch { }

            /* we need our room adding */
            int i = list.FindIndex(delegate(RoomSearchObject r) { return r.ip.Equals(IPAddress.Loopback); });

            if (i > -1)
            {
                list[i].port = CoreSettings.room_port;
            }
            else
            {
                RoomSearchObject r = new RoomSearchObject();
                r.ip = IPAddress.Loopback;
                r.port = CoreSettings.room_port;
                r.name = CoreSettings.room_name;
                r.topic = Topics.GetMainTopic(true);
                r.server = "sb0t";
                r.language = LanguageCodeToString(CoreSettings.room_language);
                r.usercount = UserPool.UserCount();
                r.hashlink = "arlnk://" + Ares.Protocols.Hashlinks.EncodeHashlink(new Ares.CommonObjects.ChannelListItem(r.name, r.name, r.port, 1, CoreSettings.room_external_ip, CoreSettings.room_external_ip));
                list.Add(r);
            }

            return list.ToArray();
        }

        private static void SaveChannelList()
        {
            Ares.PacketHandlers.AresDataPacket builder = new Ares.PacketHandlers.AresDataPacket();

            foreach (RoomSearchObject r in items)
            {
                builder.WriteString(r.name);
                builder.WriteString(r.topic);
                builder.WriteString(r.server);
                builder.WriteString(r.language);
                builder.WriteString(r.hashlink);
                builder.WriteInt16(r.usercount);
                builder.WriteIP(r.ip);
                builder.WriteInt16(r.port);
            }

            try
            {
                File.WriteAllBytes(sb0t_winGUI.RunOnceFileUpdater.sbotpath + "data/channellist1.dat", builder.ToByteArray());
            }
            catch { }
        }

        private static String ExtractServerName(String version)
        {
            if (version.Contains("sb0t"))
                return "sb0t";

            if (version.Contains("Arca Eclipse"))
                return "AE";

            if (version.Contains("Dg-x"))
                return "Dg-x";

            if (version.Contains("RUNYA"))
                return "Runya";

            if (version.ToUpper().Contains("CALLISTO"))
                return "Callisto";

            if (version.ToUpper().Contains("SURFICTION"))
                return "Surf";

            if (version.ToUpper().Contains("AURA"))
                return "AurA";

            return "Ares";
        }

        private static String LanguageCodeToString(byte code)
        {
            switch (code)
            {
                case 11:
                    return "Arabic";
                case 12:
                    return "Chinese";
                case 14:
                    return "Czech";
                case 15:
                    return "Danish";
                case 16:
                    return "Dutch";
                case 10:
                    return "English";
                case 27:
                    return "Finnish";
                case 28:
                    return "French";
                case 29:
                    return "German";
                case 30:
                    return "Italian";
                case 17:
                    return "Japanese";
                case 19:
                    return "Kirghiz";
                case 20:
                    return "Polish";
                case 21:
                    return "Portuguese";
                case 31:
                    return "Russian";
                case 22:
                    return "Slovak";
                case 23:
                    return "Spanish";
                case 25:
                    return "Swedish";
                case 26:
                    return "Turkish";
                default:
                    return "English";
            }
        }
    }

    class RoomSearchObject
    {
        public String name;
        public String topic;
        public String server;
        public String hashlink;
        public String language;
        public ushort usercount;
        public IPAddress ip;
        public ushort port;
    }

    class UdpPacketReader
    {
        private int Position = 0;
        private List<byte> Data = new List<byte>();

        public UdpPacketReader(byte[] bytes)
        {
            this.Data.Clear();
            this.Position = 0;
            this.Data.AddRange(bytes);
        }

        public int Remaining()
        {
            return this.Data.Count - this.Position;
        }

        public byte ReadByte()
        {
            byte tmp = this.Data[this.Position];
            this.Position++;
            return tmp;
        }

        public ushort ReadInt16()
        {
            ushort tmp = BitConverter.ToUInt16(this.Data.ToArray(), this.Position);
            this.Position += 2;
            return tmp;
        }

        public IPAddress ReadIP()
        {
            byte[] tmp = new byte[4];
            Array.Copy(this.Data.ToArray(), this.Position, tmp, 0, tmp.Length);
            this.Position += 4;
            return new IPAddress(tmp);
        }

        public String ReadString()
        {
            ushort len = BitConverter.ToUInt16(this.Data.ToArray(), this.Position);
            this.Position += 2;
            String str = Encoding.UTF8.GetString(this.Data.ToArray(), this.Position, len);
            this.Position += len;
            return str;
        }
    }
}
